//
// ba_gpb_syntcpserv.cpp

#include <iostream>
#include <string>

#include <boost/asio.hpp>
#include <boost/function.hpp>

#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>

#include "hiout.pb.h"

#include "TrackDataServer.hpp"

#include <cv.h>

using boost::asio::ip::tcp;

using namespace google::protobuf::io;
using namespace hi;
using namespace cv;

SynTCPTrackDataServer::SynTCPTrackDataServer(boost::function<void()> startTrackerCallback) : trackDataQueue(), startTrackerCallback(startTrackerCallback),
		completionCallback(0), hasCompletionCallback(false)  {
	// Verify that the version of the library that we linked against is
	// compatible with the version of the headers we compiled against.
	GOOGLE_PROTOBUF_VERIFY_VERSION;
}

SynTCPTrackDataServer::SynTCPTrackDataServer(boost::function<void()> startTrackerCallback,
											 boost::function<void()> completionCallback )
		: trackDataQueue(), startTrackerCallback(startTrackerCallback), completionCallback(completionCallback), hasCompletionCallback(true)  {
	// Verify that the version of the library that we linked against is
	// compatible with the version of the headers we compiled against.
	GOOGLE_PROTOBUF_VERIFY_VERSION;
}

SynTCPTrackDataServer::~SynTCPTrackDataServer() {
	// Optional:  Delete all global objects allocated by libprotobuf.
	google::protobuf::ShutdownProtobufLibrary();
}

void SynTCPTrackDataServer::run() {
	try
	{
		boost::asio::io_service io_service;
		tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), portnum));
		boost::shared_ptr< tcp::socket > scktPtr = boost::shared_ptr< tcp::socket >(new tcp::socket(io_service));
		acceptor.accept(*scktPtr);
		
		startTrackerThread(scktPtr); //calls runI	nner to continue from here! Issue with boost thread permissions
	}
	catch (std::exception& e)
	{
		std::cerr << e.what() << std::endl;
	}
}

void SynTCPTrackDataServer::runInner(boost::shared_ptr<boost::asio::ip::tcp::socket> scktPtr) {
	TrackData trackData;
	
	while (true) { //FIXNOW use thread interrupt point
		trackDataQueue.waitAndPop( trackData );
		
		int dataBufSize = trackData.ByteSize()+CodedOutputStream::VarintSize32(trackData.ByteSize());
		
		char trackDataBuf[dataBufSize];
		
		writeTrackDataToArray( trackData, trackDataBuf, dataBufSize );
		
		boost::system::error_code socket_error;
		boost::asio::write(*scktPtr, boost::asio::buffer(trackDataBuf,dataBufSize),
						   boost::asio::transfer_all(), socket_error);
		
		if (socket_error.value() !=0) break;
	}
	
	if (hasCompletionCallback) {
		completionCallback();
	}
}

void SynTCPTrackDataServer::putTrackData( hi::TrackData& trackData ) {
	trackDataQueue.push( trackData );
}

void SynTCPTrackDataServer::putTrackData( bool active, int x, int y ) {
	hi::TrackData trackData;
	MousePoint* primaryMousePoint = trackData.mutable_primary();
	
	primaryMousePoint->set_active(active);
	primaryMousePoint->set_x(x);
	primaryMousePoint->set_y(y);
	trackDataQueue.push( trackData );
}

void SynTCPTrackDataServer::putTrackData( bool active, int x, int y, bool secondActive, int secondX, int secondY ) {
	hi::TrackData trackData;
	MousePoint* primaryMousePoint = trackData.mutable_primary();
	
	primaryMousePoint->set_active(active);
	primaryMousePoint->set_x(x);
	primaryMousePoint->set_y(y);
	
	MousePoint* secondaryMousePoint = trackData.mutable_secondary();
	
	secondaryMousePoint->set_active(secondActive);
	secondaryMousePoint->set_x(secondX);
	secondaryMousePoint->set_y(secondY);
	
	trackDataQueue.push( trackData );
}

void SynTCPTrackDataServer::writeTrackDataToArray( hi::TrackData& trackData, char* dataBuf, int dataBufSize ) {
	ZeroCopyOutputStream* rawOutputPtr = new ArrayOutputStream( dataBuf, dataBufSize );
	CodedOutputStream* codedOutputPtr = new CodedOutputStream(rawOutputPtr); 
	
	codedOutputPtr->WriteVarint32( trackData.ByteSize() );
	trackData.SerializeToCodedStream( codedOutputPtr );
	
	delete codedOutputPtr;
	delete rawOutputPtr;	
}

void SynTCPTrackDataServer::startTrackerThread(boost::shared_ptr<tcp::socket> scktPtr) {

//DISABLED
//	// -- start separate thread to run tracker -- //
//	boost::thread trackerThread( boost::bind( startTrackerCallback ) );
	
	//Issues with Boost threads: It seems only the main thread can access the camera,
	//and even more strangely, it also seems there are issues using highgui other threads
	//Thus: runInner is started in a separate thread while the main thread goes on to start the tracker
	
	boost::thread serverThread( boost::bind( &SynTCPTrackDataServer::runInner, this, scktPtr ) );
	startTrackerCallback();
}